package com.spring.demo4.factory;

import com.spring.demo4.factory.Joinpoint.MyMethodInvocation;
import org.springframework.aop.AfterAdvice;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 3.ThrowsAdviceInterceptor
 * 负责将 ThrowsAdvice 异常通知包装为 MethodInterceptor类型，创建这个类型的对象的时候需要传递一个 Object 类型的参数，通常这个参数是 ThrowsAdvice类型的
 *
 * @author vhans
 */
public class MyThrowsAdviceInterceptor implements MyMethodInterceptor, AfterAdvice {

    private static final String AFTER_THROWING = "afterThrowing";

    private final Object throwsAdvice;

    private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();

    public MyThrowsAdviceInterceptor(Object throwsAdvice) {
        Assert.notNull(throwsAdvice, "Advice must not be null");
        this.throwsAdvice = throwsAdvice;
        // 获取异常通知中定义的所有方法
        Method[] methods = throwsAdvice.getClass().getMethods();
        for (Method method : methods) {
            // 方法名称为afterThrowing && 方法参数数量为1或4
            if (method.getName().equals(AFTER_THROWING)
                    && (method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
                // 获取最后一个参数类型
                Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 1];
                // 判断最后的参数类型是否是Throwable类型
                if (Throwable.class.isAssignableFrom(throwableParam)) {
                    // 缓存异常处理方法到map中(异常类型->处理方法)
                    this.exceptionHandlerMap.put(throwableParam, method);
                }
            }
        }
        // 这里如果exceptionHandlerMap为空，则抛出异常，即必须有afterThrowing方法
        if (this.exceptionHandlerMap.isEmpty()) {
            throw new IllegalArgumentException(
                    "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
        }
    }

    /**
     * 获取异常通知中自定义的处理异常方法的数量
     */
    public int getHandlerMethodCount() {
        return this.exceptionHandlerMap.size();
    }

    @Override
    public Object invoke(MyMethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        } catch (Throwable ex) {
            // 获取异常通知中自定义的异常处理方法
            Method method = getExceptionHandler(ex);
            if (method != null) {
                // 调用异常处理方法
                invokeHandlerMethod(mi, ex, method);
            }
            // 继续向外抛出异常
            throw ex;
        }
    }

    private Method getExceptionHandler(Throwable exception) {
        // 获取异常类型
        Class<?> exceptionClass = exception.getClass();
        // 从缓存中获取对应的异常处理方法
        Method method = this.exceptionHandlerMap.get(exceptionClass);
        // 来一个循环，查询传入exception下在缓存中的异常处理方法
        while (method == null && exceptionClass != Throwable.class) {
            // 获取异常的父类型
            exceptionClass = exceptionClass.getSuperclass();
            // 从缓存中查找新异常对应的处理方法
            method = this.exceptionHandlerMap.get(exceptionClass);
        }
        return method;
    }

    /**
     * 通过反射调用异常通知中的异常处理方法
     */
    private void invokeHandlerMethod(MyMethodInvocation mi, Throwable ex, Method method) throws Throwable {
        // 构建方法请求参数
        Object[] handlerArgs;
        // 若只有一个参数，则参数为异常对象
        if (method.getParameterCount() == 1) {
            handlerArgs = new Object[]{ex};
        } else { // 4个参数(方法，方法请求参数，目标对象，异常对象)
            handlerArgs = new Object[]{mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
        }
        try {
            // 通过反射调用异常通知中的方法
            method.invoke(this.throwsAdvice, handlerArgs);
        } catch (InvocationTargetException exception) {
            throw exception.getTargetException();
        }
    }
}
